package com.loongtech.core.util;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

/**
 * RSA加密解密工具类，默认密钥长度为1024
 * 重构，方便支持Android用nopadding算法
 * @author HivenYang
 * @date 2014年8月8日
 */
public class RSAEncrypt {
	private static final String KEY_ALGORITHM = "RSA";
	public static final String DEF_ALGORITHM = "RSA/ECB/PKCS1Padding";
	public static final String AND_ALGORITHM = "RSA/ECB/PKCS1Padding";

	// 密钥长度
	private int KEY_LENGTH;
	// 最大加密字节长度
	private int MAX_ENCRYPT_BLOCK;
	// 最大解密字节长度
	private int MAX_DECRYPT_BLOCK;

	public RSAEncrypt() {
		setKeyLength(1024);
	}

	/** 
	 * 私钥 
	 */
	private RSAPrivateKey privateKey;

	/** 
	 * 公钥 
	 */
	private RSAPublicKey publicKey;

	/** 
	 * 获取私钥 
	 * @return 当前的私钥对象 
	 */
	public RSAPrivateKey getPrivateKey() {
		return privateKey;
	}

	/** 
	 * 获取公钥 
	 * @return 当前的公钥对象 
	 */
	public RSAPublicKey getPublicKey() {
		return publicKey;
	}

	/**
	 * 设置密钥长度
	 * @author HivenYang
	 * @date 2014年8月8日
	 */
	public void setKeyLength(int keyLength) {
		this.KEY_LENGTH = keyLength;
		this.MAX_DECRYPT_BLOCK = this.KEY_LENGTH / 8;
		this.MAX_ENCRYPT_BLOCK = this.MAX_DECRYPT_BLOCK - 11;
	}

	/** 
	 * 随机生成密钥对 key is pubKey and val is priKey
	 */
	public Map.Entry<String, String> genKeyPair() {
		KeyPairGenerator keyPairGen = null;
		try {
			keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
		} catch(NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		keyPairGen.initialize(KEY_LENGTH, new SecureRandom());
		KeyPair keyPair = keyPairGen.generateKeyPair();
		this.privateKey = (RSAPrivateKey) keyPair.getPrivate();
		this.publicKey = (RSAPublicKey) keyPair.getPublic();
		String pubKey = Utils.base64Encode(this.publicKey.getEncoded());
		String priKey = Utils.base64Encode(this.privateKey.getEncoded());
		return new java.util.AbstractMap.SimpleEntry<String, String>(pubKey,priKey);
	}
	
	/** 
	 * 从文件中输入流中加载公钥 
	 * @param in 公钥输入流 
	 * @throws Exception 加载公钥时产生的异常 
	 */
	public void loadPublicKey(InputStream in) throws Exception {
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String readLine = null;
			StringBuilder sb = new StringBuilder();
			while((readLine = br.readLine()) != null) {
				if(readLine.charAt(0) == '-') {
					continue;
				} else {
					sb.append(readLine);
					sb.append('\r');
				}
			}
			loadPublicKey(sb.toString());
		} catch(IOException e) {
			throw new Exception("公钥数据流读取错误");
		} catch(NullPointerException e) {
			throw new Exception("公钥输入流为空");
		}
	}


	/** 
	 * 从字符串中加载公钥 
	 * @param publicKeyStr 公钥数据字符串 
	 * @throws Exception 加载公钥时产生的异常 
	 */
	public void loadPublicKey(String publicKeyStr) throws Exception {
		try {
			byte[] buffer = Utils.base64Decode(publicKeyStr);
			KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
			X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
			this.publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);
		} catch(NoSuchAlgorithmException e) {
			throw new Exception("无此算法");
		} catch(InvalidKeySpecException e) {
			throw new Exception("公钥非法");
		} catch(NullPointerException e) {
			throw new Exception("公钥数据为空");
		}
	}

	/** 
	 * 从文件中加载私钥 
	 * @param keyFileName 私钥文件名 
	 * @return 是否成功 
	 * @throws Exception  
	 */
	public void loadPrivateKey(InputStream in) throws Exception {
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String readLine = null;
			StringBuilder sb = new StringBuilder();
			while((readLine = br.readLine()) != null) {
				if(readLine.charAt(0) == '-') {
					continue;
				} else {
					sb.append(readLine);
					sb.append('\r');
				}
			}
			loadPrivateKey(sb.toString());
		} catch(IOException e) {
			throw new Exception("私钥数据读取错误");
		} catch(NullPointerException e) {
			throw new Exception("私钥输入流为空");
		}
	}

	/**
	 * 加载私钥
	 * @param privateKeyStr
	 * @throws Exception
	 */
	public void loadPrivateKey(String privateKeyStr) throws Exception {
		try {
			byte[] buffer = Utils.base64Decode(privateKeyStr);
			PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
			KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
			this.privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
		} catch(NoSuchAlgorithmException e) {
			throw new Exception("无此算法");
		} catch(InvalidKeySpecException e) {
			throw new Exception("私钥非法");
		} catch(NullPointerException e) {
			throw new Exception("私钥数据为空");
		}
	}

	/**
	 * 默认使用padding的算法
	 * @param plainData
	 * @return
	 * @throws Exception
	 */
	public byte[] encryptBlockByPublicKey(byte[] plainData) throws Exception {
		return encryptBlockByPublicKey(plainData, DEF_ALGORITHM);
	}

	/** 
	 * 公钥分段加密过程 
	 * @param plainData 明文数据 
	 * @return 
	 * @throws Exception 加密过程中的异常信息 
	 */
	public byte[] encryptBlockByPublicKey(byte[] plainData, String alg) throws Exception {
		if(publicKey == null) {
			throw new Exception("加密公钥为空, 请设置");
		}
		Cipher cipher = null;
		try {
			// 使用默认RSA
			cipher = Cipher.getInstance(null == alg ? DEF_ALGORITHM : alg);
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			// 分段加密
			int inputLen = plainData.length;
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			int offSet = 0;
			byte[] cache;
			int i = 0;
			// 对数据分段加密
			while(inputLen - offSet > 0) {
				if(inputLen - offSet > MAX_ENCRYPT_BLOCK) {
					cache = cipher.doFinal(plainData, offSet, MAX_ENCRYPT_BLOCK);
				} else {
					cache = cipher.doFinal(plainData, offSet, inputLen - offSet);
				}
				out.write(cache, 0, cache.length);
				i++;
				offSet = i * MAX_ENCRYPT_BLOCK;
			}
			byte[] encryptedData = out.toByteArray();
			out.close();
			return encryptedData;

		} catch(NoSuchAlgorithmException e) {
			throw new Exception("无此加密算法");
		} catch(NoSuchPaddingException e) {
			e.printStackTrace();
			return null;
		} catch(InvalidKeyException e) {
			throw new Exception("加密公钥非法,请检查");
		} catch(IllegalBlockSizeException e) {
			throw new Exception("明文长度非法");
		} catch(BadPaddingException e) {
			throw new Exception("明文数据已损坏");
		}
	}

	public byte[] encryptBlockByPrivateKey(byte[] plainData) throws Exception {
		return encryptBlockByPrivateKey(plainData, DEF_ALGORITHM);
	}

	/** 
	 * 私钥分段加密过程 
	 * @param plainData 明文数据 
	 * @return 
	 * @throws Exception 加密过程中的异常信息 
	 */
	public byte[] encryptBlockByPrivateKey(byte[] plainData, String alg) throws Exception {
		if(privateKey == null) {
			throw new Exception("加密私钥为空, 请设置");
		}
		Cipher cipher = null;
		try {
			// 使用默认RSA
			cipher = Cipher.getInstance(null == alg ? DEF_ALGORITHM : alg);
			cipher.init(Cipher.ENCRYPT_MODE, privateKey);

			int inputLen = plainData.length;
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			int offSet = 0;
			byte[] cache;
			int i = 0;
			// 对数据分段加密
			while(inputLen - offSet > 0) {
				if(inputLen - offSet > MAX_ENCRYPT_BLOCK) {
					cache = cipher.doFinal(plainData, offSet, MAX_ENCRYPT_BLOCK);
				} else {
					cache = cipher.doFinal(plainData, offSet, inputLen - offSet);
				}
				out.write(cache, 0, cache.length);
				i++;
				offSet = i * MAX_ENCRYPT_BLOCK;
			}
			byte[] encryptedData = out.toByteArray();
			out.close();
			return encryptedData;

		} catch(NoSuchAlgorithmException e) {
			throw new Exception("无此加密算法");
		} catch(NoSuchPaddingException e) {
			e.printStackTrace();
			return null;
		} catch(InvalidKeyException e) {
			throw new Exception("加密私钥非法,请检查");
		} catch(IllegalBlockSizeException e) {
			throw new Exception("明文长度非法");
		} catch(BadPaddingException e) {
			throw new Exception("明文数据已损坏");
		}
	}

	public byte[] decryptBlockByPrivateKey(byte[] cipherData) throws Exception {
		return decryptBlockByPrivateKey(cipherData, DEF_ALGORITHM);
	}

	/** 
	 * 私钥分段解密过程 
	 * @param privateKey 私钥 
	 * @param cipherData 密文数据 
	 * @return 明文 
	 * @throws Exception 解密过程中的异常信息 
	 */
	public byte[] decryptBlockByPrivateKey(byte[] cipherData, String alg) throws Exception {
		if(privateKey == null) {
			throw new Exception("解密私钥为空, 请设置");
		}
		Cipher cipher = null;
		try {
			// 使用默认RSA
			cipher = Cipher.getInstance(null == alg ? DEF_ALGORITHM : alg);
			cipher.init(Cipher.DECRYPT_MODE, privateKey);

			int inputLen = cipherData.length;
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			int offSet = 0;
			byte[] cache;
			int i = 0;
			// 对数据分段解密
			while(inputLen - offSet > 0) {
				if(inputLen - offSet > MAX_DECRYPT_BLOCK) {
					cache = cipher.doFinal(cipherData, offSet, MAX_DECRYPT_BLOCK);
				} else {
					cache = cipher.doFinal(cipherData, offSet, inputLen - offSet);
				}
				out.write(cache, 0, cache.length);
				i++;
				offSet = i * MAX_DECRYPT_BLOCK;
			}
			byte[] plainData = out.toByteArray();
			out.close();
			return plainData;

		} catch(NoSuchAlgorithmException e) {
			throw new Exception("无此解密算法");
		} catch(NoSuchPaddingException e) {
			e.printStackTrace();
			return null;
		} catch(InvalidKeyException e) {
			throw new Exception("解密私钥非法,请检查");
		} catch(IllegalBlockSizeException e) {
			throw new Exception("密文长度非法");
		} catch(BadPaddingException e) {
			throw new Exception("密文数据已损坏");
		}
	}

	public byte[] decryptBlockByPublicKey(byte[] cipherData) throws Exception {
		return decryptBlockByPublicKey(cipherData, DEF_ALGORITHM);
	}

	/** 
	 * 公钥分段解密过程 
	 * @param cipherData 密文数据 
	 * @return 明文 
	 * @throws Exception 解密过程中的异常信息 
	 */
	public byte[] decryptBlockByPublicKey(byte[] cipherData, String alg) throws Exception {
		if(publicKey == null) {
			throw new Exception("解密公钥为空, 请设置");
		}
		Cipher cipher = null;
		try {
			// 使用默认RSA
			cipher = Cipher.getInstance(null == alg ? DEF_ALGORITHM : alg);
			cipher.init(Cipher.DECRYPT_MODE, publicKey);

			int inputLen = cipherData.length;
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			int offSet = 0;
			byte[] cache;
			int i = 0;
			// 对数据分段解密
			while(inputLen - offSet > 0) {
				if(inputLen - offSet > MAX_DECRYPT_BLOCK) {
					cache = cipher.doFinal(cipherData, offSet, MAX_DECRYPT_BLOCK);
				} else {
					cache = cipher.doFinal(cipherData, offSet, inputLen - offSet);
				}
				out.write(cache, 0, cache.length);
				i++;
				offSet = i * MAX_DECRYPT_BLOCK;
			}
			byte[] plainData = out.toByteArray();
			out.close();
			return plainData;

		} catch(NoSuchAlgorithmException e) {
			throw new Exception("无此解密算法");
		} catch(NoSuchPaddingException e) {
			e.printStackTrace();
			return null;
		} catch(InvalidKeyException e) {
			throw new Exception("解密公钥非法,请检查");
		} catch(IllegalBlockSizeException e) {
			throw new Exception("密文长度非法");
		} catch(BadPaddingException e) {
			throw new Exception("密文数据已损坏");
		}
	}

	public byte[] decryptByPublicKey(byte[] cipherData) throws Exception {
		return decryptByPublicKey(cipherData, DEF_ALGORITHM);
	}

	/** 
	 * 公钥解密过程 
	 * @param cipherData 密文数据 
	 * @return 明文 
	 * @throws Exception 解密过程中的异常信息 
	 */
	public byte[] decryptByPublicKey(byte[] cipherData, String alg) throws Exception {
		if(publicKey == null) {
			throw new Exception("解密公钥为空, 请设置");
		}
		Cipher cipher = null;
		try {
			// 使用默认RSA
			cipher = Cipher.getInstance(null == alg ? DEF_ALGORITHM : alg);
			cipher.init(Cipher.DECRYPT_MODE, publicKey);

			return cipher.doFinal(cipherData);

		} catch(NoSuchAlgorithmException e) {
			throw new Exception("无此解密算法");
		} catch(NoSuchPaddingException e) {
			e.printStackTrace();
			return null;
		} catch(InvalidKeyException e) {
			throw new Exception("解密公钥非法,请检查");
		} catch(IllegalBlockSizeException e) {
			throw new Exception("密文长度非法");
		} catch(BadPaddingException e) {
			throw new Exception("密文数据已损坏");
		}
	}

	/** ------------------------------------------------------------------------------------------------ 
	下面是静态方法
	 **/
	private static final String SIGN_ALGORITHMS = "SHA1WithRSA";

	/**
	* RSA签名
	* @param content 待签名数据
	* @param privateKey 商户私钥
	* @param input_charset 编码格式
	* @return 签名值
	*/
	public static String sign(String content, String privateKey, String input_charset) {
		try {
			PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
			KeyFactory keyf = KeyFactory.getInstance("RSA");
			PrivateKey priKey = keyf.generatePrivate(priPKCS8);
			java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
			signature.initSign(priKey);
			signature.update(content.getBytes(input_charset));
			return Base64.encode(signature.sign());
		} catch(Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	* RSA验签名检查
	* @param content 待签名数据
	* @param sign 签名值
	* @param public_key RSA公钥
	* @param input_charset 编码格式 utf-8
	* @return 布尔值
	*/
	public static boolean verify(String content, String sign, String public_key, String input_charset) {
		try {
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			byte[] encodedKey = Base64.decode(public_key);
			PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
			java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
			signature.initVerify(pubKey);
			signature.update(content.getBytes(input_charset));
			return signature.verify(Base64.decode(sign));
		} catch(Exception e) {
			e.printStackTrace();
		}

		return false;
	}

	/**
	* 得到私钥
	* @param key 密钥字符串（经过base64编码）
	* @throws Exception
	*/
	public static PrivateKey getPrivateKey(String key) throws Exception {
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(key));
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		return keyFactory.generatePrivate(keySpec);
	}

	/**
	* 得到公钥
	* @param key 密钥字符串（经过base64编码）
	* @throws Exception
	*/
	public static PublicKey getPublicKey(String key) throws Exception {
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(key));
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		return keyFactory.generatePublic(keySpec);
	}
}
